﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace psafth.P3DecoderV2
{
    public partial class P3DecoderV2 : Form
    {
        delegate void StringParameterDelegate(string value);

        private BackgroundWorker _bw;

        private bool _HasClientConnection = false;

        public List<BackgroundWorker> Transponders
        {
            get;
            set;
        }

        public ConcurrentQueue<byte[]> CommandQueue
        {
            get;
            set;
        }

        public P3DecoderV2()
        {
            CommandQueue = new ConcurrentQueue<byte[]>();

            InitializeComponent();

            _bw = new BackgroundWorker();

            _bw.DoWork += _bw_DoWork;
            _bw.ProgressChanged += _bw_ProgressChanged;
            _bw.RunWorkerCompleted += _bw_RunWorkerCompleted;

            _bw.WorkerSupportsCancellation = true;


            BackgroundWorker status = new StatusLoop();
            status.ProgressChanged += _bw_ProgressChanged;
            status.RunWorkerAsync();

            this.Transponders = new List<BackgroundWorker>();

            BackgroundWorker fredrik = new TransponderPassing() { TransponderId = 4777687 };
            fredrik.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker johan = new TransponderPassing() { TransponderId = 3581439 };
            johan.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker per = new TransponderPassing() { TransponderId = 9075509 };
            per.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker marth = new TransponderPassing() { TransponderId = 9530045 };
            marth.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker daniel = new TransponderPassing() { TransponderId = 8929082 };
            daniel.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker anders = new TransponderPassing() { TransponderId = 4917087 };
            anders.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker fredrick = new TransponderPassing() { TransponderId = 6006846 };
            fredrick.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker staffan = new TransponderPassing() { TransponderId = 9221969 };
            staffan.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker peter = new TransponderPassing() { TransponderId = 9796583 };
            peter.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker magnus = new TransponderPassing() { TransponderId = 9454387 };
            magnus.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker lennart = new TransponderPassing() { TransponderId = 5487897 };
            lennart.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker sven = new TransponderPassing() { TransponderId = 3056340 };
            sven.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker stefan = new TransponderPassing() { TransponderId = 6641950 };
            stefan.ProgressChanged += _bw_ProgressChanged;

            BackgroundWorker unknown = new TransponderPassing() { TransponderId = 7654321 };
            unknown.ProgressChanged += _bw_ProgressChanged;

            Transponders.Add(fredrik);
            Transponders.Add(johan);
            Transponders.Add(per);
            Transponders.Add(marth);
            //Transponders.Add(daniel);
            Transponders.Add(anders);
            //Transponders.Add(fredrick);
            Transponders.Add(staffan);
            //Transponders.Add(peter);
            Transponders.Add(magnus);
            Transponders.Add(lennart);
            Transponders.Add(sven);
            //Transponders.Add(stefan);
            //Transponders.Add(unknown);

            DoThings();
        }

        void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            _HasClientConnection = false;

            string message = string.Format("{0}\n", "Disconnected!");
            AppendToTextBox(message);

            DoThings();
        }

        void _bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!_bw.CancellationPending)
            {
                TcpListener server = new TcpListener(IPAddress.Any, 5403);

                server.Start();

                TcpClient client = server.AcceptTcpClient(); // Get client connection

                string message = string.Format("Connected: {0} \n", client.Client.LocalEndPoint.ToString());

                _HasClientConnection = true;

                AppendToTextBox(message);

                client.ReceiveTimeout = 15000;
                client.SendTimeout = 15000;

                NetworkStream netStream = client.GetStream();

                netStream.ReadTimeout = 15000;
                netStream.WriteTimeout = 15000;

                // Never seen a message larger than ~58 bytes. I think 64 bytes / 512 bit is the frame size.
                Byte[] data = new Byte[64];

                while (client.Connected)
                {
                    if (netStream.DataAvailable)
                    {
                        // Read the first batch of the TcpServer response bytes.
                        int messageSize = netStream.Read(data, 0, data.Length);

                        // Remove unessessssaarrryy bytes.
                        byte[] bytes = new byte[messageSize];

                        for (int i = 0; i < messageSize; i++)
                        {
                            bytes[i] = data[i];
                        }

                        // Trying to avoid problems with the use of psafth.AMB.TransferObject and psafth.P3DecoderV2.TransferObject
                        psafth.AMB.TransferObject receivedData = new psafth.AMB.TransferObject(bytes);

                        // Only do things if the CRC is valid.
                        if (receivedData.Message.CRCValid)
                        {
                            // Check the type of the message
                            switch (receivedData.MessageType)
                            {
                                // If of type GET_TIME, reply with current date time.
                                case AMB.AMBMessageType.GET_TIME:
                                    AMB.GetTime obj = new AMB.GetTime(receivedData.Data);
                                    CommandQueue.Enqueue(Commands.GetTime());
                                    break;
                            }
                        }
                    }


                    // Check if there is something to send
                    if (!CommandQueue.IsEmpty)
                    {
                        // Check if the stream can be written to
                        if (netStream.CanWrite)
                        {
                            try
                            {
                                // As long as there are items in the queue
                                while (!CommandQueue.IsEmpty)
                                {
                                    byte[] command;

                                    if (CommandQueue.TryDequeue(out command))
                                    {
                                        // Write to the stream and flush it.
                                        netStream.Write(command, 0, command.Length);
                                        netStream.Flush();
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                // TODO: Nothing for now. Maybe implement logging och some kind of error message to the console later.
                            }
                        }
                    }
                }

                // Stop the server
                server.Stop();

                // Break the loop
                break;
            }
        }

        void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.UserState != null)
            {
                if (e.UserState is TransferObject)
                {
                    TransferObject transfer = (TransferObject)e.UserState;

                    if (_HasClientConnection)
                        CommandQueue.Enqueue(transfer.Data);

                    string message = string.Format("{0}: {1}\n", transfer.Type, transfer.Information);

                    AppendToTextBox(message);
                }
            }
        }

        void AppendToTextBox(string str)
        {
            if (InvokeRequired)
            {
                // We're not in the UI thread, so we need to call BeginInvoke
                BeginInvoke(new StringParameterDelegate(AppendToTextBox), new object[] { str });
                return;
            }

            this.rtbConsole.AppendText(str);
            this.rtbConsole.ScrollToCaret();
        }

        public void DoThings()
        {
            _bw.RunWorkerAsync();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            DoThings();
        }

        private void cbReportPassings_CheckedChanged(object sender, EventArgs e)
        {
            if (cbReportPassings.Checked)
            {
                this.Transponders.ForEach(t => {
                    t.RunWorkerAsync();
                    Thread.Sleep(100);
                });
            }
            else
            {
                this.Transponders.ForEach(t => t.CancelAsync());
            }
        }
    }
}
